// Copyright 2022 Jeroen van Nugteren

// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
// and associated documentation files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

// include header
#include "dfscale.hh"

// rat common headers
#include "rat/common/error.hh"

// code specific to Rat
namespace rat{namespace dm{

	// constructor
	DFScale::DFScale(){
		set_name("Scale");
	}

	// constructcor
	DFScale::DFScale(
		const ShDistFunPr &df, 
		const fltp offset, 
		const fltp scale, 
		const fltp max) : DFScale(){
		
		// set to self
		set_df(df);
		set_offset(offset);
		set_scale(scale);
		set_max(max);

		// check
		is_valid(true);
	}

	// factory
	ShDFScalePr DFScale::create(){
		return std::make_shared<DFScale>();
	}

	// factory
	ShDFScalePr DFScale::create(
		const ShDistFunPr &df, 
		const fltp offset, 
		const fltp scale, 
		const fltp max){
		return std::make_shared<DFScale>(df,offset,scale,max);
	}

	// setters
	void DFScale::set_df(const ShDistFunPr &df){
		df_ = df;
	}

	void DFScale::set_offset(const fltp offset){
		offset_ = offset;
	}

	void DFScale::set_scale(const fltp scale){
		scale_ = scale;
	}

	void DFScale::set_max(const fltp max){
		max_ = max;
	}

	// getters
	const ShDistFunPr& DFScale::get_df()const{
		return df_;
	}

	fltp DFScale::get_offset()const{
		return offset_;
	}

	fltp DFScale::get_scale()const{
		return scale_;
	}
	
	fltp DFScale::get_max()const{
		return max_;
	}

	// get bounding box
	arma::Mat<fltp>::fixed<2,2> DFScale::get_bounding() const{
		if(df_==NULL)rat_throw_line("distance function not set");
		// return box
		return df_->get_bounding();
	}

	// get fixed points
	arma::Mat<fltp> DFScale::get_fixed(const fltp abstol) const{
		if(df_==NULL)rat_throw_line("distance function not set");
		return df_->get_fixed(abstol);
	}

	// distance function
	arma::Col<fltp> DFScale::calc_distance(const arma::Mat<fltp> &p) const{
		return arma::min(offset_ + scale_*df_->calc_distance(p),arma::Col<fltp>(p.n_rows,arma::fill::ones)*max_);
	}

	// type string for serialization
	std::string DFScale::get_type(){
		return "rat::dm::dfscale";
	}

	// validity check
	bool DFScale::is_valid(const bool enable_throws)const{
		// check user input
		if(df_==NULL){if(enable_throws){rat_throw_line("no distance function set");} return false;};
		if(!df_->is_valid(enable_throws))return false;
		if(scale_<=0){if(enable_throws){rat_throw_line("scale must be larger than zero");} return false;};
		if(max_<=0){if(enable_throws){rat_throw_line("max must be larger than zero");} return false;};
		return true;
	}

	// method for serialization into json
	void DFScale::serialize(
		Json::Value &js, 
		cmn::SList &list) const{

		// parent
		DistFun::serialize(js,list);

		// store type
		js["type"] = get_type();

		// scaling
		js["offset"] = offset_;
		js["scale"] = scale_;
		js["max"] = max_;

		// serialize distance functions
		js["distfun"] = cmn::Node::serialize_node(df_,list);
	}

	// method for deserialisation from json
	void DFScale::deserialize(
		const Json::Value &js, cmn::DSList &list, 
		const cmn::NodeFactoryMap &factory_list, 
		const boost::filesystem::path &pth){

		// parent
		DistFun::deserialize(js,list,factory_list,pth);

		// variables
		offset_ = js["offset"].ASFLTP();
		scale_ = js["scale"].ASFLTP();
		max_ = js["max"].ASFLTP();
		
		// distance function
		df_ = Node::deserialize_node<DistFun>(js["distfun"], list, factory_list, pth);
	}

}}